Here, we will explore the use of LIMMA (“linear models for microarray data”) for performing linear modelling.

The original limma publication (2004!!) is here: https://www.ncbi.nlm.nih.gov/pubmed/16646809 For a proper explanation of the statitical model see: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5373812/

To save repeating the work of other in describing the use of limma, I refer you to this introduction from Kasper D. Hansen: https://kasperdanielhansen.github.io/genbioconductor/html/limma.html.

Note that the data used in the above is microarray data in an ExpressionSet object. However, limma is agnostic to the type of input data and is perfectly suitable for proteomics data so long as it’s reasonable to assume the quantification values are approximately gaussian distributed. For this reason, the quantification values should first be log transformed.

As stated in the documentation for the MSnSet class (https://www.rdocumentation.org/packages/MSnbase/versions/1.20.7/topics/MSnSet-class): " The MSnSet class is derived from the eSet class and mimics the ExpressionSet class classically used for microarray data." It’s therefore relatively straightforward to use limma with proteomics data in a MSnSet.

# load packages
library(tidyverse)
library(limma)
library(biobroom)
library(Hmisc)
library(MSnbase)
# set up standardised plotting scheme
theme_set(theme_bw(base_size = 20) +
            theme(panel.grid.major=element_blank(),
                  panel.grid.minor=element_blank(),
                  aspect.ratio=1))
cbPalette <- c("#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7", "#999999")

Again, we read in the MSnSets and subset to the samples of interest

total_protein_quant <- readRDS("../raw/total_res_pro_agg_norm.rds")
rbp_protein_quant <- readRDS("../raw/rbp_res_pro_agg_norm.rds")
# identify the samples we want to keep
samples_to_keep <- grep("M_|G1_", pData(total_protein_quant)$Sample_name)
print(samples_to_keep)
[1] 2 3 4 5 6 7
total_protein_quant <- total_protein_quant[,samples_to_keep]
rbp_protein_quant <- rbp_protein_quant[,samples_to_keep]

Let’s start by applying limma to the total protein quantification data only. First of all we need to create a design matrix. We can do this from the pData since this contains the information about the samples

condition <- pData(total_protein_quant)$Condition
design <- model.matrix(~condition)
print(design)
  (Intercept) conditionM
1           1          1
2           1          1
3           1          1
4           1          0
5           1          0
6           1          0
attr(,"assign")
[1] 0 1
attr(,"contrasts")
attr(,"contrasts")$condition
[1] "contr.treatment"

Then we fit the model using this design and update the estimates for the standard errors for each coefficient using the eBayes function. As expected, there is a relationship between mean intensity and variance, although this is almost all limited to the very low intensity values. Limma will use this relationship to moderate the standard errors for the coefficients estimated such that the per-protein variance estimates are “squeezed” towards the expectation derived from other proteins with similar mean intensity.

tidy(total_protein_quant, addPheno=TRUE) %>%# "tidy" the object, e.g make it into a tidy data format --> long
  group_by(protein, Condition) %>% # group by protein and condition
  dplyr::summarise(mean=mean(value), sqrt_sd=sqrt(sd(value))) %>% # mean and var for each group
  ggplot(aes(mean, sqrt_sd)) + # plot mean(intensity) vs sqrt(sd(intensity))
  geom_point(size=0.1) +
  geom_smooth(se=FALSE, method="loess") + # local regression
  xlab("Mean intensity") +
  ylab("sqrt(sd/mean)")

Below we run limma to idenify the proteins with a significant change in abundance between conditions

total_fit_lm <- lmFit(exprs(total_protein_quant), design) # fit model to each protein
total_fit_lm_e <- eBayes(total_fit_lm) # shrink std errors to abundance vs. stdev trend
total_fit_lm_e_c <- contrasts.fit(total_fit_lm_e, coefficients=c("conditionM")) # extract results for coefficient of interest
p_value_status <- ifelse(total_fit_lm_e_c$p.value[,'conditionM']<0.01, "sig", "not_sig") # identify significant changes
# plot
limma::plotMA(total_fit_lm_e_c, status=p_value_status,
       col=c(cbPalette[6], "black"), cex=c(0.5,0.1), main="")

Note that most of these changes are relatively slight (<2-fold)

# Extract all results from limma (n=Inf)
all_results <- topTable(total_fit_lm_e_c, coef = "conditionM", n = Inf)
my_volcanoplot <- function(topTableResults){
  p <- topTableResults %>%
    mutate(sig=ifelse(adj.P.Val<0.01, "sig.", "not sig.")) %>% # add "sig" column
    ggplot(aes(logFC, -log10(P.Value), colour=sig)) +
    geom_point(size=0.25) +
    scale_colour_manual(values=c("black", cbPalette[6]), name="") # manually adjust colours
  
  return(p)
}
my_volcanoplot(all_results)

OK, so it’s easy to perform the pairwise comparison. What about changes in RNA binding? For this, we need combine the two MSnSets into a single ExpressionSet

intersecting_proteins <- intersect(rownames(total_protein_quant), rownames(rbp_protein_quant))
total_for_combination <- total_protein_quant[intersecting_proteins,]
rbp_for_combination <- rbp_protein_quant[intersecting_proteins,]
# make the column names for the two MSnSets unique
colnames(total_for_combination) <- paste0(colnames(total_for_combination), "_Total")
colnames(rbp_for_combination) <- paste0(colnames(rbp_for_combination), "_OOPS")
# make the ExpressionSet
combined_intensities <- ExpressionSet(cbind(exprs(total_for_combination), exprs(rbp_for_combination)))
# Add the feature data
fData(combined_intensities) <- fData(total_for_combination)
# Add the phenotype data
pData(combined_intensities) <- rbind(pData(total_for_combination), pData(rbp_for_combination))
pData(combined_intensities)$Condition <- factor(pData(combined_intensities)$Condition, level=c("M","G1"))
pData(combined_intensities)$Type <- factor(pData(combined_intensities)$Type, level=c("Total","OOPS"))
dim(exprs(combined_intensities))
[1] 1916   12
print(head(data.frame(exprs(combined_intensities)), 2))
print(head(fData(combined_intensities), 2))
print(head(pData(combined_intensities), 2))
condition <- combined_intensities$Condition
type <- combined_intensities$Type
sample_name <- combined_intensities$Sample_name
design <- model.matrix(~condition*type) 
rna_binding_fit <- lmFit(combined_intensities, design)
rna_binding_fit <- contrasts.fit(rna_binding_fit, coefficients="conditionG1:typeOOPS")
rna_binding_fit <- eBayes(rna_binding_fit)
rna_binding_p_value_status <- ifelse(rna_binding_fit$p.value[,'conditionG1:typeOOPS']<0.01, "sig", "not_sig")
limma::plotMA(rna_binding_fit, status=rna_binding_p_value_status, values=c("sig", "not_sig"),
              col=c(cbPalette[6], "black"), cex=c(0.5,0.1), main="")

Below, we summarise the number of signficant p-values (post BH FDR correction) using a 1% FDR threshold. The second table also demands that the point estimate for the fold change is > 2.

summary(decideTests(rna_binding_fit, p.value=0.01, adjust.method="BH"))
       conditionG1:typeOOPS
Down                    432
NotSig                 1047
Up                      437
summary(decideTests(rna_binding_fit, p.value=0.01, adjust.method="BH", lfc=1))
       conditionG1:typeOOPS
Down                    102
NotSig                 1714
Up                      100
all_rna_binding_results <- topTable(rna_binding_fit, coef = "conditionG1:typeOOPS", n = Inf, confint=TRUE)
my_volcanoplot(all_rna_binding_results)

So again, lots of RNA binding changes!

Now, let’s compare the results from the two methods. To do this, we will merge together the results from the two methods.

M_G1_simple_lm <- readRDS("../results/M_G1_changes_in_RNA_binding_linear_model.rds")
compare_methods <- all_rna_binding_results %>%
  dplyr::select("logFC", "AveExpr", "adj.P.Val", "P.Value") %>%
  merge(M_G1_simple_lm, by.x="row.names", by.y="protein")

First, let’s tabulate the proteins significant in each method

lm_sig <- ifelse(compare_methods$lm_BH<0.01, "lm sig", "lm not sig")
limma_sig <- ifelse(compare_methods$adj.P.Val<0.01, "limma sig", "limma not sig")
print(table(lm_sig, limma_sig))
            limma_sig
lm_sig       limma not sig limma sig
  lm not sig          1022       201
  lm sig                25       668
compare_methods$sig_status <- interaction(lm_sig, limma_sig)

OK, so most proteins with significant change in RNA binding using lm or limma are significant in both, although limma does indicate more proteins have a significant change. Note that only 25/1916 proteins are significant by lm only.

What about if we separate by the lm model used, e.g +/- tag

fit <- compare_methods$fit
print(table(lm_sig, limma_sig, fit))
, , fit = With_tag

            limma_sig
lm_sig       limma not sig limma sig
  lm not sig           541       139
  lm sig                18       349

, , fit = Without_tag

            limma_sig
lm_sig       limma not sig limma sig
  lm not sig           481        62
  lm sig                 7       319

OK, so lm and limma results are more similar if the lm model did not include the tag. This is no suprise since the limma fomula we used did not include the tag covariate so this is the closest comparison

First, let’s compare the p-values. Note that the p-values are usually lower in limma. The second plot shows the threshold for the maximum p-value which results in an estimated FDR < 1% for both methods.

max_p_sig_lm <- compare_methods %>% filter(lm_BH<0.01) %>% pull(lm_p_value) %>% max()
max_p_sig_limma <- compare_methods %>% filter(adj.P.Val<0.01) %>% pull(P.Value) %>% max()
p <- compare_methods %>%
  ggplot(aes(log10(lm_p_value), log10(P.Value), colour=fit)) +
  geom_point() +
  scale_colour_manual(values=cbPalette) +
  geom_abline(slope=1, linetype=2, colour=cbPalette[6]) +
  xlab("lm") +
  ylab("limma") 
print(p)

print(p +
  geom_vline(xintercept=log10(max_p_sig_lm), linetype=2) +
  geom_hline(yintercept=log10(max_p_sig_limma), linetype=2))

compare_methods %>%
  mutate(binned_ave_exprs=cut2(AveExpr, g=10)) %>%
  ggplot(aes(log10(lm_p_value), log10(P.Value), colour=fit)) +
  geom_point() +
  geom_abline(slope=1, linetype=2) +
  #scale_colour_manual(values=cbPalette) +
  xlab("lm") +
  ylab("limma") +
  facet_wrap(~binned_ave_exprs)

compare_methods %>%
  ggplot(aes(abs(logFC), fill=sig_status)) +
  geom_histogram() +
  facet_grid(sig_status~., scales="free") +
  scale_fill_discrete(guide=FALSE) +
  theme(strip.text=element_text(size=15))

NA
NA

Now, the fold change estimates. Note that the estimates for the fold change are not changed by the bayesian shrinkage of the coefficient standard errors.

compare_methods %>% ggplot(aes(log10(lm_fold_change), log10(logFC))) +
  geom_point(size=1, alpha=0.2) +
  xlab("lm") +
  ylab("limma") +
  ggtitle("Estimated fold changes")

Finally, let’s explore some of the proteins which were detected as having a significant change in RNA binding with only one method. Remember from above that the p-values for lm and limma are very well correlated so we’re looking here at slight differences close to the 1% FDR thresholds.

# Function to plot the intensities values
plotIntensities <- function(obj){
  
  p <- tidy(obj, addPheno=TRUE) %>%
    ggplot(aes(Condition, value, colour=Type, group=Type)) +
    geom_point() +
    stat_summary(geom="line", fun.y=mean) +
    facet_wrap(~gene, scales='free') +
    ylab("Intensity (log2)")
    
  invisible(p)
}
print(plotIntensities(combined_intensities["A0AVT1",]))

Let’s look at the proteins which are “lm only”. We’ll ignore those where we used the TMT in our lm model since this was not included in the limma model so that may be another reason for differences in the p-values.

lm_only <- compare_methods %>% filter(sig_status=='lm sig.limma not sig', fit=="Without_tag") %>%
  dplyr::select(Row.names, lm_fold_change, P.Value, adj.P.Val, lm_p_value, lm_BH, lm_std_error) %>% # select columns
  arrange(desc(P.Value)) # arrange by limma p-value (descending order)
print(lm_only)

Now let’s plot these proteins. Notice that in all cases, the replicates are very tightly distributed.

print(plotIntensities(combined_intensities[lm_only$Row.names,]))

In some cases, the intensity values are near identical (see below). While it’s possible the biological variability for this protein is very low, it seems unlikely that the exact same amount of RNA-bound protein was recovered given the expected technical variability from the OOPS protocol and sample preparation. A much more likely explanation is that these intensity values are so similar simply by chance. This is the (reasonable) assumption by which limma alters the standard deviations for the coefficients using features with similar abundance.

# Heterogeneous nuclear ribonucleoprotein U-like protein 1
# HNRNPUL1
# Acts as a basic transcriptional regulator. Represses basic transcription driven by several virus and cellular promoters.
# When associated with BRD7, activates transcription of glucocorticoid-responsive promoter in the absence of
# ligand-stimulation. Plays also a role in mRNA processing and transport. Binds avidly to poly(G) and poly(C) RNA
# homopolymers in vitro.
tidy(combined_intensities, addPheno=TRUE) %>%
  filter(gene=="Q9BUJ2", Type=="OOPS", Condition=="M") %>%
  dplyr::select(Condition, Replicate, value)

Below, we explore the observed intensity values for some of the proteins which are significant according only to limma. Note that these have relatively large

limma_only <- compare_methods %>% filter(sig_status=='lm not sig.limma sig', fit=="Without_tag") %>%
  dplyr::select(Row.names, lm_fold_change, P.Value, adj.P.Val, lm_p_value, lm_BH, lm_std_error) %>%
  arrange(desc(lm_p_value))
print(head(limma_only))
print(plotIntensities(combined_intensities[limma_only$Row.names[1:9],]))

Let’s go back to that plot of mean vs sqrt and see how the proteins compare depending on whether they were detected as signficant in each method.

As expected, those significant in just lm have relatively low observed std. dev. and those significant in just limma have relatively high std. dev.

mean_sd_data <- tidy(total_protein_quant, addPheno=TRUE) %>%# "tidy" the object, e.g make it into a tidy data format --> long
  group_by(protein, Condition) %>% # group by protein and condition
  dplyr::summarise(mean=mean(value), sqrt_sd=sqrt(sd(value))) %>% # mean and stdev for each group
  ungroup() %>%
  merge(compare_methods, by.x="protein", by.y="Row.names") %>% # merge in the results from the two methods
  filter(fit=="Without_tag") # Only keep those proteins fitted without the tag covariate in lm
# remake the basic plot showing the relationship
p_basic <- mean_sd_data %>%
  ggplot(aes(mean, sqrt_sd)) + 
  xlab("Mean intensity") +
  ylab("Coefficient of variance\n(sd/mean)") +
  xlim(0, NA) + ylim(0, NA) # include 0,0
print(p_basic + geom_point(size=0.2, alpha=0.5) + geom_smooth(se=FALSE))

p_density <- p_basic +
  geom_density_2d(aes(colour=sig_status), alpha=0.5, size=0.5) + # density of points
  scale_colour_manual(values=cbPalette[c(2:4,6)])# set colours
print(p_density)

Finally, we can get limma to return the signficant changes using both p-value and log-fold change thresholds using the TREAT method (https://www.ncbi.nlm.nih.gov/pubmed/19176553). Note that this is not the same as thresholding on the p-value and the point estimate for the fold change as limma is actually testing the null hypothesis that the fold change is less than our specified threshold. To be explicit, let’s check the difference

sig_changes_p <- topTable(rna_binding_fit, coef = "conditionG1:typeOOPS", n = Inf, p.value=0.01, adjust.method="fdr", confint=0.95)
sig_changes_p_logfc_point_estimate <- sig_changes_p[abs(sig_changes_p$logFC)>1,]
cat(sprintf("%s proteins pass adjusted p-value threshold, of which %s pass the threshold on fold change point estimate\n", 
            nrow(sig_changes_p), nrow(sig_changes_p_logfc_point_estimate)))
869 proteins pass adjusted p-value threshold, of which 202 pass the threshold on fold change point estimate
rna_binding_fit_treat <- treat(rna_binding_fit,lfc=1,trend=TRUE) # Test null hypothesis than change is <2-fold 
sig_changes_p_logfc <- topTreat(rna_binding_fit_treat, coef = "conditionG1:typeOOPS", n = Inf,
                p.value=0.01, lfc=1, adjust.method="fdr", confint=0.95)
cat(sprintf("%s proteins pass the combined adjusted p-value threshold + fold change > 2\n",  nrow(sig_changes_p_logfc)))
10 proteins pass the combined adjusted p-value threshold + fold change > 2

And below we reproduce our volcano plot including the 95% confidence interval and highlight those proteins which have < 1% FDR and an absolute fold change significantly greater than 2. Below, we can see that many of the proteins with a fold change (FC) point estimate > 2 have a 95% confidence interval that overlaps the dashed lines for >2-fold change. TREAT also takes the multiple testing into account so it’s even more conservative than just using the 95% CI shown below.

.tmp_df <- all_rna_binding_results
.tmp_df$sig <- ifelse(.tmp_df$P.Value<=0.01, "<1% FDR", ">1% FDR") # add "sig" column
.tmp_df$sig[rownames(.tmp_df) %in% rownames(sig_changes_p_logfc_point_estimate)] <- "<1% FDR. FC point estimate < 2"
.tmp_df$sig[rownames(.tmp_df) %in% rownames(sig_changes_p_logfc)] <- "<1% FDR. TREAT FC < 2"
.tmp_df$SE <- sqrt(rna_binding_fit$s2.post) * rna_binding_fit$stdev.unscaled[,1]
p <- .tmp_df %>%
  ggplot(aes(logFC, -log10(P.Value), colour=sig)) +
  geom_point(size=1) +
  scale_colour_manual(values=c(cbPalette[c(6,2,7)], "grey20"), name="") + # manually adjust colours
  geom_vline(xintercept=1, linetype=2, colour="grey70") +
  geom_vline(xintercept=-1, linetype=2, colour="grey70") +
  theme(legend.position="top", legend.direction=2)
  
print(p)

print(p + geom_errorbarh(aes(xmin=CI.L, xmax=CI.R)))

Of course, the threshold for the fold changes you would are interested in

Save out results objects for later notebooks

saveRDS(all_rna_binding_results, "../results/limma_rna_binding_results.rds")
saveRDS(rna_binding_fit_treat, "../results/limma_rna_binding_results_treat.rds")
saveRDS(compare_methods, "../results/compare_methods_rna_binding_results.rds")
LS0tCnRpdGxlOiAiVXNpbmcgTElNTUEgaW4gcHJvdGVvbWljcyIKb3V0cHV0OgogIHBkZl9kb2N1bWVudDogZGVmYXVsdAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgpIZXJlLCB3ZSB3aWxsIGV4cGxvcmUgdGhlIHVzZSBvZiBMSU1NQSAo4oCcbGluZWFyIG1vZGVscyBmb3IgbWljcm9hcnJheSBkYXRh4oCdKSBmb3IgcGVyZm9ybWluZyBsaW5lYXIgbW9kZWxsaW5nLgoKVGhlIG9yaWdpbmFsIGxpbW1hIHB1YmxpY2F0aW9uICgyMDA0ISEpIGlzIGhlcmU6IGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcHVibWVkLzE2NjQ2ODA5CkZvciBhIHByb3BlciBleHBsYW5hdGlvbiBvZiB0aGUgc3RhdGl0aWNhbCBtb2RlbCBzZWU6IGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzUzNzM4MTIvCgpUbyBzYXZlIHJlcGVhdGluZyB0aGUgd29yayBvZiBvdGhlciBpbiBkZXNjcmliaW5nIHRoZSB1c2Ugb2YgYGxpbW1hYCwgSSByZWZlciB5b3UgdG8gdGhpcyBpbnRyb2R1Y3Rpb24gZnJvbSBLYXNwZXIgRC4gSGFuc2VuOgpodHRwczovL2thc3BlcmRhbmllbGhhbnNlbi5naXRodWIuaW8vZ2VuYmlvY29uZHVjdG9yL2h0bWwvbGltbWEuaHRtbC4gCgpOb3RlIHRoYXQgdGhlIGRhdGEgdXNlZCBpbiB0aGUgYWJvdmUgaXMgbWljcm9hcnJheSBkYXRhIGluIGFuIGBFeHByZXNzaW9uU2V0YCBvYmplY3QuIEhvd2V2ZXIsIGxpbW1hIGlzIGFnbm9zdGljIHRvIHRoZSB0eXBlIG9mIGlucHV0IGRhdGEgYW5kIGlzIHBlcmZlY3RseSBzdWl0YWJsZSBmb3IgcHJvdGVvbWljcyBkYXRhIHNvIGxvbmcgYXMgaXQncyByZWFzb25hYmxlIHRvIGFzc3VtZSB0aGUgcXVhbnRpZmljYXRpb24gdmFsdWVzIGFyZSBhcHByb3hpbWF0ZWx5IGdhdXNzaWFuIGRpc3RyaWJ1dGVkLiBGb3IgdGhpcyByZWFzb24sIHRoZSBxdWFudGlmaWNhdGlvbiB2YWx1ZXMgc2hvdWxkIGZpcnN0IGJlIGxvZyB0cmFuc2Zvcm1lZC4gCgpBcyBzdGF0ZWQgaW4gdGhlIGRvY3VtZW50YXRpb24gZm9yIHRoZSBNU25TZXQgY2xhc3MgKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9NU25iYXNlL3ZlcnNpb25zLzEuMjAuNy90b3BpY3MvTVNuU2V0LWNsYXNzKTogIiBUaGUgYE1TblNldGAgY2xhc3MgaXMgZGVyaXZlZCBmcm9tIHRoZSBgZVNldGAgY2xhc3MgYW5kIG1pbWljcyB0aGUgYEV4cHJlc3Npb25TZXRgIGNsYXNzIGNsYXNzaWNhbGx5IHVzZWQgZm9yIG1pY3JvYXJyYXkgZGF0YS4iIEl0J3MgdGhlcmVmb3JlIHJlbGF0aXZlbHkgc3RyYWlnaHRmb3J3YXJkIHRvIHVzZSBgbGltbWFgIHdpdGggcHJvdGVvbWljcyBkYXRhIGluIGEgYE1TblNldGAuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBsb2FkIHBhY2thZ2VzCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGxpbW1hKQpsaWJyYXJ5KGJpb2Jyb29tKQpsaWJyYXJ5KEhtaXNjKQpsaWJyYXJ5KE1TbmJhc2UpCgojIHNldCB1cCBzdGFuZGFyZGlzZWQgcGxvdHRpbmcgc2NoZW1lCnRoZW1lX3NldCh0aGVtZV9idyhiYXNlX3NpemUgPSAyMCkgKwogICAgICAgICAgICB0aGVtZShwYW5lbC5ncmlkLm1ham9yPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgcGFuZWwuZ3JpZC5taW5vcj1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIGFzcGVjdC5yYXRpbz0xKSkKCmNiUGFsZXR0ZSA8LSBjKCIjRTY5RjAwIiwgIiM1NkI0RTkiLCAiIzAwOUU3MyIsICIjRjBFNDQyIiwgIiMwMDcyQjIiLCAiI0Q1NUUwMCIsICIjQ0M3OUE3IiwgIiM5OTk5OTkiKQpgYGAKCkFnYWluLCB3ZSByZWFkIGluIHRoZSBNU25TZXRzIGFuZCBzdWJzZXQgdG8gdGhlIHNhbXBsZXMgb2YgaW50ZXJlc3QKYGBge3J9CnRvdGFsX3Byb3RlaW5fcXVhbnQgPC0gcmVhZFJEUygiLi4vcmF3L3RvdGFsX3Jlc19wcm9fYWdnX25vcm0ucmRzIikKcmJwX3Byb3RlaW5fcXVhbnQgPC0gcmVhZFJEUygiLi4vcmF3L3JicF9yZXNfcHJvX2FnZ19ub3JtLnJkcyIpCgojIGlkZW50aWZ5IHRoZSBzYW1wbGVzIHdlIHdhbnQgdG8ga2VlcApzYW1wbGVzX3RvX2tlZXAgPC0gZ3JlcCgiTV98RzFfIiwgcERhdGEodG90YWxfcHJvdGVpbl9xdWFudCkkU2FtcGxlX25hbWUpCnByaW50KHNhbXBsZXNfdG9fa2VlcCkKCnRvdGFsX3Byb3RlaW5fcXVhbnQgPC0gdG90YWxfcHJvdGVpbl9xdWFudFssc2FtcGxlc190b19rZWVwXQpyYnBfcHJvdGVpbl9xdWFudCA8LSByYnBfcHJvdGVpbl9xdWFudFssc2FtcGxlc190b19rZWVwXQpgYGAKCkxldCdzIHN0YXJ0IGJ5IGFwcGx5aW5nIGxpbW1hIHRvIHRoZSB0b3RhbCBwcm90ZWluIHF1YW50aWZpY2F0aW9uIGRhdGEgb25seS4gRmlyc3Qgb2YgYWxsIHdlIG5lZWQgdG8gY3JlYXRlIGEgZGVzaWduIG1hdHJpeC4gV2UgY2FuIGRvIHRoaXMgZnJvbSB0aGUgYHBEYXRhYCBzaW5jZSB0aGlzIGNvbnRhaW5zIHRoZSBpbmZvcm1hdGlvbiBhYm91dCB0aGUgc2FtcGxlcwpgYGB7cn0KY29uZGl0aW9uIDwtIHBEYXRhKHRvdGFsX3Byb3RlaW5fcXVhbnQpJENvbmRpdGlvbgpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH5jb25kaXRpb24pCnByaW50KGRlc2lnbikKYGBgCgpUaGVuIHdlIGZpdCB0aGUgbW9kZWwgdXNpbmcgdGhpcyBkZXNpZ24gYW5kIHVwZGF0ZSB0aGUgZXN0aW1hdGVzIGZvciB0aGUgc3RhbmRhcmQgZXJyb3JzIGZvciBlYWNoIGNvZWZmaWNpZW50IHVzaW5nIHRoZSBgZUJheWVzYCBmdW5jdGlvbi4gQXMgZXhwZWN0ZWQsIHRoZXJlIGlzIGEgcmVsYXRpb25zaGlwIGJldHdlZW4gbWVhbiBpbnRlbnNpdHkgYW5kIHZhcmlhbmNlLCBhbHRob3VnaCB0aGlzIGlzIGFsbW9zdCBhbGwgbGltaXRlZCB0byB0aGUgdmVyeSBsb3cgaW50ZW5zaXR5IHZhbHVlcy4gTGltbWEgd2lsbCB1c2UgdGhpcyByZWxhdGlvbnNoaXAgdG8gbW9kZXJhdGUgdGhlIHN0YW5kYXJkIGVycm9ycyBmb3IgdGhlIGNvZWZmaWNpZW50cyBlc3RpbWF0ZWQgc3VjaCB0aGF0IHRoZSBwZXItcHJvdGVpbiB2YXJpYW5jZSBlc3RpbWF0ZXMgYXJlICJzcXVlZXplZCIgdG93YXJkcyB0aGUgZXhwZWN0YXRpb24gZGVyaXZlZCBmcm9tIG90aGVyIHByb3RlaW5zIHdpdGggc2ltaWxhciBtZWFuIGludGVuc2l0eS4KCmBgYHtyfQoKdGlkeSh0b3RhbF9wcm90ZWluX3F1YW50LCBhZGRQaGVubz1UUlVFKSAlPiUjICJ0aWR5IiB0aGUgb2JqZWN0LCBlLmcgbWFrZSBpdCBpbnRvIGEgdGlkeSBkYXRhIGZvcm1hdCAtLT4gbG9uZwogIGdyb3VwX2J5KHByb3RlaW4sIENvbmRpdGlvbikgJT4lICMgZ3JvdXAgYnkgcHJvdGVpbiBhbmQgY29uZGl0aW9uCiAgZHBseXI6OnN1bW1hcmlzZShtZWFuPW1lYW4odmFsdWUpLCBzcXJ0X3NkPXNxcnQoc2QodmFsdWUpKSkgJT4lICMgbWVhbiBhbmQgdmFyIGZvciBlYWNoIGdyb3VwCiAgZ2dwbG90KGFlcyhtZWFuLCBzcXJ0X3NkKSkgKyAjIHBsb3QgbWVhbihpbnRlbnNpdHkpIHZzIHNxcnQoc2QoaW50ZW5zaXR5KSkKICBnZW9tX3BvaW50KHNpemU9MC4xKSArCiAgZ2VvbV9zbW9vdGgoc2U9RkFMU0UsIG1ldGhvZD0ibG9lc3MiKSArICMgbG9jYWwgcmVncmVzc2lvbgogIHhsYWIoIk1lYW4gaW50ZW5zaXR5IikgKwogIHlsYWIoInNxcnQoc2QvbWVhbikiKQoKYGBgCgpCZWxvdyB3ZSBydW4gbGltbWEgdG8gaWRlbmlmeSB0aGUgcHJvdGVpbnMgd2l0aCBhIHNpZ25pZmljYW50IGNoYW5nZSBpbiBhYnVuZGFuY2UgYmV0d2VlbiBjb25kaXRpb25zCmBgYHtyfQp0b3RhbF9maXRfbG0gPC0gbG1GaXQoZXhwcnModG90YWxfcHJvdGVpbl9xdWFudCksIGRlc2lnbikgIyBmaXQgbW9kZWwgdG8gZWFjaCBwcm90ZWluCnRvdGFsX2ZpdF9sbV9lIDwtIGVCYXllcyh0b3RhbF9maXRfbG0pICMgc2hyaW5rIHN0ZCBlcnJvcnMgdG8gYWJ1bmRhbmNlIHZzLiBzdGRldiB0cmVuZAoKdG90YWxfZml0X2xtX2VfYyA8LSBjb250cmFzdHMuZml0KHRvdGFsX2ZpdF9sbV9lLCBjb2VmZmljaWVudHM9YygiY29uZGl0aW9uTSIpKSAjIGV4dHJhY3QgcmVzdWx0cyBmb3IgY29lZmZpY2llbnQgb2YgaW50ZXJlc3QKCnBfdmFsdWVfc3RhdHVzIDwtIGlmZWxzZSh0b3RhbF9maXRfbG1fZV9jJHAudmFsdWVbLCdjb25kaXRpb25NJ108MC4wMSwgInNpZyIsICJub3Rfc2lnIikgIyBpZGVudGlmeSBzaWduaWZpY2FudCBjaGFuZ2VzCgojIHBsb3QKbGltbWE6OnBsb3RNQSh0b3RhbF9maXRfbG1fZV9jLCBzdGF0dXM9cF92YWx1ZV9zdGF0dXMsCiAgICAgICBjb2w9YyhjYlBhbGV0dGVbNl0sICJibGFjayIpLCBjZXg9YygwLjUsMC4xKSwgbWFpbj0iIikKCgoKYGBgCk5vdGUgdGhhdCBtb3N0IG9mIHRoZXNlIGNoYW5nZXMgYXJlIHJlbGF0aXZlbHkgc2xpZ2h0ICg8Mi1mb2xkKQpgYGB7cn0KIyBFeHRyYWN0IGFsbCByZXN1bHRzIGZyb20gbGltbWEgKG49SW5mKQphbGxfcmVzdWx0cyA8LSB0b3BUYWJsZSh0b3RhbF9maXRfbG1fZV9jLCBjb2VmID0gImNvbmRpdGlvbk0iLCBuID0gSW5mKQoKbXlfdm9sY2Fub3Bsb3QgPC0gZnVuY3Rpb24odG9wVGFibGVSZXN1bHRzKXsKICBwIDwtIHRvcFRhYmxlUmVzdWx0cyAlPiUKICAgIG11dGF0ZShzaWc9aWZlbHNlKGFkai5QLlZhbDwwLjAxLCAic2lnLiIsICJub3Qgc2lnLiIpKSAlPiUgIyBhZGQgInNpZyIgY29sdW1uCiAgICBnZ3Bsb3QoYWVzKGxvZ0ZDLCAtbG9nMTAoUC5WYWx1ZSksIGNvbG91cj1zaWcpKSArCiAgICBnZW9tX3BvaW50KHNpemU9MC4yNSkgKwogICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9YygiYmxhY2siLCBjYlBhbGV0dGVbNl0pLCBuYW1lPSIiKSAjIG1hbnVhbGx5IGFkanVzdCBjb2xvdXJzCiAgCiAgcmV0dXJuKHApCn0KCm15X3ZvbGNhbm9wbG90KGFsbF9yZXN1bHRzKQpgYGAKCgpPSywgc28gaXQncyBlYXN5IHRvIHBlcmZvcm0gdGhlIHBhaXJ3aXNlIGNvbXBhcmlzb24uIFdoYXQgYWJvdXQgY2hhbmdlcyBpbiBSTkEgYmluZGluZz8gRm9yIHRoaXMsIHdlIG5lZWQgY29tYmluZSB0aGUgdHdvIE1TblNldHMgaW50byBhIHNpbmdsZSBFeHByZXNzaW9uU2V0CmBgYHtyfQoKaW50ZXJzZWN0aW5nX3Byb3RlaW5zIDwtIGludGVyc2VjdChyb3duYW1lcyh0b3RhbF9wcm90ZWluX3F1YW50KSwgcm93bmFtZXMocmJwX3Byb3RlaW5fcXVhbnQpKQoKdG90YWxfZm9yX2NvbWJpbmF0aW9uIDwtIHRvdGFsX3Byb3RlaW5fcXVhbnRbaW50ZXJzZWN0aW5nX3Byb3RlaW5zLF0KcmJwX2Zvcl9jb21iaW5hdGlvbiA8LSByYnBfcHJvdGVpbl9xdWFudFtpbnRlcnNlY3RpbmdfcHJvdGVpbnMsXQoKIyBtYWtlIHRoZSBjb2x1bW4gbmFtZXMgZm9yIHRoZSB0d28gTVNuU2V0cyB1bmlxdWUKY29sbmFtZXModG90YWxfZm9yX2NvbWJpbmF0aW9uKSA8LSBwYXN0ZTAoY29sbmFtZXModG90YWxfZm9yX2NvbWJpbmF0aW9uKSwgIl9Ub3RhbCIpCmNvbG5hbWVzKHJicF9mb3JfY29tYmluYXRpb24pIDwtIHBhc3RlMChjb2xuYW1lcyhyYnBfZm9yX2NvbWJpbmF0aW9uKSwgIl9PT1BTIikKCiMgbWFrZSB0aGUgRXhwcmVzc2lvblNldApjb21iaW5lZF9pbnRlbnNpdGllcyA8LSBFeHByZXNzaW9uU2V0KGNiaW5kKGV4cHJzKHRvdGFsX2Zvcl9jb21iaW5hdGlvbiksIGV4cHJzKHJicF9mb3JfY29tYmluYXRpb24pKSkKCiMgQWRkIHRoZSBmZWF0dXJlIGRhdGEKZkRhdGEoY29tYmluZWRfaW50ZW5zaXRpZXMpIDwtIGZEYXRhKHRvdGFsX2Zvcl9jb21iaW5hdGlvbikKCiMgQWRkIHRoZSBwaGVub3R5cGUgZGF0YQpwRGF0YShjb21iaW5lZF9pbnRlbnNpdGllcykgPC0gcmJpbmQocERhdGEodG90YWxfZm9yX2NvbWJpbmF0aW9uKSwgcERhdGEocmJwX2Zvcl9jb21iaW5hdGlvbikpCgpwRGF0YShjb21iaW5lZF9pbnRlbnNpdGllcykkQ29uZGl0aW9uIDwtIGZhY3RvcihwRGF0YShjb21iaW5lZF9pbnRlbnNpdGllcykkQ29uZGl0aW9uLCBsZXZlbD1jKCJNIiwiRzEiKSkKcERhdGEoY29tYmluZWRfaW50ZW5zaXRpZXMpJFR5cGUgPC0gZmFjdG9yKHBEYXRhKGNvbWJpbmVkX2ludGVuc2l0aWVzKSRUeXBlLCBsZXZlbD1jKCJUb3RhbCIsIk9PUFMiKSkKCmRpbShleHBycyhjb21iaW5lZF9pbnRlbnNpdGllcykpCnByaW50KGhlYWQoZGF0YS5mcmFtZShleHBycyhjb21iaW5lZF9pbnRlbnNpdGllcykpLCAyKSkKcHJpbnQoaGVhZChmRGF0YShjb21iaW5lZF9pbnRlbnNpdGllcyksIDIpKQpwcmludChoZWFkKHBEYXRhKGNvbWJpbmVkX2ludGVuc2l0aWVzKSwgMikpCmBgYAoKCmBgYHtyfQpjb25kaXRpb24gPC0gY29tYmluZWRfaW50ZW5zaXRpZXMkQ29uZGl0aW9uCnR5cGUgPC0gY29tYmluZWRfaW50ZW5zaXRpZXMkVHlwZQpzYW1wbGVfbmFtZSA8LSBjb21iaW5lZF9pbnRlbnNpdGllcyRTYW1wbGVfbmFtZQoKZGVzaWduIDwtIG1vZGVsLm1hdHJpeCh+Y29uZGl0aW9uKnR5cGUpIAoKcm5hX2JpbmRpbmdfZml0IDwtIGxtRml0KGNvbWJpbmVkX2ludGVuc2l0aWVzLCBkZXNpZ24pCgpybmFfYmluZGluZ19maXQgPC0gY29udHJhc3RzLmZpdChybmFfYmluZGluZ19maXQsIGNvZWZmaWNpZW50cz0iY29uZGl0aW9uRzE6dHlwZU9PUFMiKQpybmFfYmluZGluZ19maXQgPC0gZUJheWVzKHJuYV9iaW5kaW5nX2ZpdCkKCnJuYV9iaW5kaW5nX3BfdmFsdWVfc3RhdHVzIDwtIGlmZWxzZShybmFfYmluZGluZ19maXQkcC52YWx1ZVssJ2NvbmRpdGlvbkcxOnR5cGVPT1BTJ108MC4wMSwgInNpZyIsICJub3Rfc2lnIikKCmxpbW1hOjpwbG90TUEocm5hX2JpbmRpbmdfZml0LCBzdGF0dXM9cm5hX2JpbmRpbmdfcF92YWx1ZV9zdGF0dXMsIHZhbHVlcz1jKCJzaWciLCAibm90X3NpZyIpLAogICAgICAgICAgICAgIGNvbD1jKGNiUGFsZXR0ZVs2XSwgImJsYWNrIiksIGNleD1jKDAuNSwwLjEpLCBtYWluPSIiKQpgYGAKQmVsb3csIHdlIHN1bW1hcmlzZSB0aGUgbnVtYmVyIG9mIHNpZ25maWNhbnQgcC12YWx1ZXMgKHBvc3QgQkggRkRSIGNvcnJlY3Rpb24pIHVzaW5nIGEgMSUgRkRSIHRocmVzaG9sZC4gVGhlIHNlY29uZCB0YWJsZSBhbHNvIGRlbWFuZHMgdGhhdCB0aGUgcG9pbnQgZXN0aW1hdGUgZm9yIHRoZSBmb2xkIGNoYW5nZSBpcyA+IDIuCmBgYHtyfQpzdW1tYXJ5KGRlY2lkZVRlc3RzKHJuYV9iaW5kaW5nX2ZpdCwgcC52YWx1ZT0wLjAxLCBhZGp1c3QubWV0aG9kPSJCSCIpKQpzdW1tYXJ5KGRlY2lkZVRlc3RzKHJuYV9iaW5kaW5nX2ZpdCwgcC52YWx1ZT0wLjAxLCBhZGp1c3QubWV0aG9kPSJCSCIsIGxmYz0xKSkKYGBgCgpgYGB7cn0KYWxsX3JuYV9iaW5kaW5nX3Jlc3VsdHMgPC0gdG9wVGFibGUocm5hX2JpbmRpbmdfZml0LCBjb2VmID0gImNvbmRpdGlvbkcxOnR5cGVPT1BTIiwgbiA9IEluZiwgY29uZmludD1UUlVFKQoKbXlfdm9sY2Fub3Bsb3QoYWxsX3JuYV9iaW5kaW5nX3Jlc3VsdHMpCmBgYApTbyBhZ2FpbiwgbG90cyBvZiBSTkEgYmluZGluZyBjaGFuZ2VzIQoKTm93LCBsZXQncyBjb21wYXJlIHRoZSByZXN1bHRzIGZyb20gdGhlIHR3byBtZXRob2RzLiBUbyBkbyB0aGlzLCB3ZSB3aWxsIG1lcmdlIHRvZ2V0aGVyIHRoZSByZXN1bHRzIGZyb20gdGhlIHR3byBtZXRob2RzLgpgYGB7cn0KTV9HMV9zaW1wbGVfbG0gPC0gcmVhZFJEUygiLi4vcmVzdWx0cy9NX0cxX2NoYW5nZXNfaW5fUk5BX2JpbmRpbmdfbGluZWFyX21vZGVsLnJkcyIpCgpjb21wYXJlX21ldGhvZHMgPC0gYWxsX3JuYV9iaW5kaW5nX3Jlc3VsdHMgJT4lCiAgZHBseXI6OnNlbGVjdCgibG9nRkMiLCAiQXZlRXhwciIsICJhZGouUC5WYWwiLCAiUC5WYWx1ZSIpICU+JQogIG1lcmdlKE1fRzFfc2ltcGxlX2xtLCBieS54PSJyb3cubmFtZXMiLCBieS55PSJwcm90ZWluIikKYGBgCgpGaXJzdCwgbGV0J3MgdGFidWxhdGUgdGhlIHByb3RlaW5zIHNpZ25pZmljYW50IGluIGVhY2ggbWV0aG9kCmBgYHtyfQpsbV9zaWcgPC0gaWZlbHNlKGNvbXBhcmVfbWV0aG9kcyRsbV9CSDwwLjAxLCAibG0gc2lnIiwgImxtIG5vdCBzaWciKQpsaW1tYV9zaWcgPC0gaWZlbHNlKGNvbXBhcmVfbWV0aG9kcyRhZGouUC5WYWw8MC4wMSwgImxpbW1hIHNpZyIsICJsaW1tYSBub3Qgc2lnIikKCnByaW50KHRhYmxlKGxtX3NpZywgbGltbWFfc2lnKSkKCmNvbXBhcmVfbWV0aG9kcyRzaWdfc3RhdHVzIDwtIGludGVyYWN0aW9uKGxtX3NpZywgbGltbWFfc2lnKQpgYGAKCk9LLCBzbyBtb3N0IHByb3RlaW5zIHdpdGggc2lnbmlmaWNhbnQgY2hhbmdlIGluIFJOQSBiaW5kaW5nIHVzaW5nIGBsbWAgb3IgYGxpbW1hYCBhcmUgc2lnbmlmaWNhbnQgaW4gYm90aCwgYWx0aG91Z2ggYGxpbW1hYCBkb2VzIGluZGljYXRlIG1vcmUgcHJvdGVpbnMgaGF2ZSBhIHNpZ25pZmljYW50IGNoYW5nZS4gTm90ZSB0aGF0IG9ubHkgMjUvMTkxNiBwcm90ZWlucyBhcmUgc2lnbmlmaWNhbnQgYnkgYGxtYCBvbmx5LgoKV2hhdCBhYm91dCBpZiB3ZSBzZXBhcmF0ZSBieSB0aGUgYGxtYCBtb2RlbCB1c2VkLCBlLmcgKy8tIHRhZwpgYGB7cn0KZml0IDwtIGNvbXBhcmVfbWV0aG9kcyRmaXQKcHJpbnQodGFibGUobG1fc2lnLCBsaW1tYV9zaWcsIGZpdCkpCgpgYGAKT0ssIHNvIGBsbWAgYW5kIGBsaW1tYWAgcmVzdWx0cyBhcmUgbW9yZSBzaW1pbGFyIGlmIHRoZSBgbG1gIG1vZGVsIGRpZCBub3QgaW5jbHVkZSB0aGUgdGFnLiBUaGlzIGlzIG5vIHN1cHJpc2Ugc2luY2UgdGhlIGBsaW1tYWAgZm9tdWxhIHdlIHVzZWQgZGlkIG5vdCBpbmNsdWRlIHRoZSB0YWcgY292YXJpYXRlIHNvIHRoaXMgaXMgdGhlIGNsb3Nlc3QgY29tcGFyaXNvbgoKRmlyc3QsIGxldCdzIGNvbXBhcmUgdGhlIHAtdmFsdWVzLiBOb3RlIHRoYXQgdGhlIHAtdmFsdWVzIGFyZSB1c3VhbGx5IGxvd2VyIGluIGBsaW1tYWAuIFRoZSBzZWNvbmQgcGxvdCBzaG93cyB0aGUgdGhyZXNob2xkIGZvciB0aGUgbWF4aW11bSBwLXZhbHVlIHdoaWNoIHJlc3VsdHMgaW4gYW4gZXN0aW1hdGVkIEZEUiA8IDElIGZvciBib3RoIG1ldGhvZHMuIApgYGB7cn0KbWF4X3Bfc2lnX2xtIDwtIGNvbXBhcmVfbWV0aG9kcyAlPiUgZmlsdGVyKGxtX0JIPDAuMDEpICU+JSBwdWxsKGxtX3BfdmFsdWUpICU+JSBtYXgoKQptYXhfcF9zaWdfbGltbWEgPC0gY29tcGFyZV9tZXRob2RzICU+JSBmaWx0ZXIoYWRqLlAuVmFsPDAuMDEpICU+JSBwdWxsKFAuVmFsdWUpICU+JSBtYXgoKQoKcCA8LSBjb21wYXJlX21ldGhvZHMgJT4lCiAgZ2dwbG90KGFlcyhsb2cxMChsbV9wX3ZhbHVlKSwgbG9nMTAoUC5WYWx1ZSksIGNvbG91cj1maXQpKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jYlBhbGV0dGUpICsKICBnZW9tX2FibGluZShzbG9wZT0xLCBsaW5ldHlwZT0yLCBjb2xvdXI9Y2JQYWxldHRlWzZdKSArCiAgeGxhYigibG0iKSArCiAgeWxhYigibGltbWEiKSAKCnByaW50KHApCgpwcmludChwICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9bG9nMTAobWF4X3Bfc2lnX2xtKSwgbGluZXR5cGU9MikgKwogIGdlb21faGxpbmUoeWludGVyY2VwdD1sb2cxMChtYXhfcF9zaWdfbGltbWEpLCBsaW5ldHlwZT0yKSkKYGBgCgoKYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0KCmNvbXBhcmVfbWV0aG9kcyAlPiUKICBtdXRhdGUoYmlubmVkX2F2ZV9leHBycz1jdXQyKEF2ZUV4cHIsIGc9MTApKSAlPiUKICBnZ3Bsb3QoYWVzKGxvZzEwKGxtX3BfdmFsdWUpLCBsb2cxMChQLlZhbHVlKSwgY29sb3VyPWZpdCkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fYWJsaW5lKHNsb3BlPTEsIGxpbmV0eXBlPTIpICsKICAjc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y2JQYWxldHRlKSArCiAgeGxhYigibG0iKSArCiAgeWxhYigibGltbWEiKSArCiAgZmFjZXRfd3JhcCh+YmlubmVkX2F2ZV9leHBycykKCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9MTB9Cgpjb21wYXJlX21ldGhvZHMgJT4lCiAgZ2dwbG90KGFlcyhhYnMobG9nRkMpLCBmaWxsPXNpZ19zdGF0dXMpKSArCiAgZ2VvbV9oaXN0b2dyYW0oKSArCiAgZmFjZXRfZ3JpZChzaWdfc3RhdHVzfi4sIHNjYWxlcz0iZnJlZSIpICsKICBzY2FsZV9maWxsX2Rpc2NyZXRlKGd1aWRlPUZBTFNFKSArCiAgdGhlbWUoc3RyaXAudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xNSkpCiAgCiAgCmBgYAoKTm93LCB0aGUgZm9sZCBjaGFuZ2UgZXN0aW1hdGVzLiBOb3RlIHRoYXQgdGhlIGVzdGltYXRlcyBmb3IgdGhlIGZvbGQgY2hhbmdlIGFyZSBub3QgY2hhbmdlZCBieSB0aGUgYmF5ZXNpYW4gc2hyaW5rYWdlIG9mIHRoZSBjb2VmZmljaWVudCBzdGFuZGFyZCBlcnJvcnMuCmBgYHtyfQpjb21wYXJlX21ldGhvZHMgJT4lIGdncGxvdChhZXMobG9nMTAobG1fZm9sZF9jaGFuZ2UpLCBsb2cxMChsb2dGQykpKSArCiAgZ2VvbV9wb2ludChzaXplPTEsIGFscGhhPTAuMikgKwogIHhsYWIoImxtIikgKwogIHlsYWIoImxpbW1hIikgKwogIGdndGl0bGUoIkVzdGltYXRlZCBmb2xkIGNoYW5nZXMiKQpgYGAKRmluYWxseSwgbGV0J3MgZXhwbG9yZSBzb21lIG9mIHRoZSBwcm90ZWlucyB3aGljaCB3ZXJlIGRldGVjdGVkIGFzIGhhdmluZyBhIHNpZ25pZmljYW50IGNoYW5nZSBpbiBSTkEgYmluZGluZyB3aXRoIG9ubHkgb25lIG1ldGhvZC4gUmVtZW1iZXIgZnJvbSBhYm92ZSB0aGF0IHRoZSBwLXZhbHVlcyBmb3IgbG0gYW5kIGxpbW1hIGFyZSB2ZXJ5IHdlbGwgY29ycmVsYXRlZCBzbyB3ZSdyZSBsb29raW5nIGhlcmUgYXQgc2xpZ2h0IGRpZmZlcmVuY2VzIGNsb3NlIHRvIHRoZSAxJSBGRFIgdGhyZXNob2xkcy4gCgoKYGBge3J9CiMgRnVuY3Rpb24gdG8gcGxvdCB0aGUgaW50ZW5zaXRpZXMgdmFsdWVzCnBsb3RJbnRlbnNpdGllcyA8LSBmdW5jdGlvbihvYmopewogIAogIHAgPC0gdGlkeShvYmosIGFkZFBoZW5vPVRSVUUpICU+JQogICAgZ2dwbG90KGFlcyhDb25kaXRpb24sIHZhbHVlLCBjb2xvdXI9VHlwZSwgZ3JvdXA9VHlwZSkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBzdGF0X3N1bW1hcnkoZ2VvbT0ibGluZSIsIGZ1bi55PW1lYW4pICsKICAgIGZhY2V0X3dyYXAofmdlbmUsIHNjYWxlcz0nZnJlZScpICsKICAgIHlsYWIoIkludGVuc2l0eSAobG9nMikiKQogICAgCiAgaW52aXNpYmxlKHApCn0KCgpwcmludChwbG90SW50ZW5zaXRpZXMoY29tYmluZWRfaW50ZW5zaXRpZXNbIkEwQVZUMSIsXSkpCgpgYGAKTGV0J3MgbG9vayBhdCB0aGUgcHJvdGVpbnMgd2hpY2ggYXJlICJsbSBvbmx5Ii4gV2UnbGwgaWdub3JlIHRob3NlIHdoZXJlIHdlIHVzZWQgdGhlIFRNVCBpbiBvdXIgbG0gbW9kZWwgc2luY2UgdGhpcyB3YXMgbm90IGluY2x1ZGVkIGluIHRoZSBsaW1tYSBtb2RlbCBzbyB0aGF0IG1heSBiZSBhbm90aGVyIHJlYXNvbiBmb3IgZGlmZmVyZW5jZXMgaW4gdGhlIHAtdmFsdWVzLgoKCmBgYHtyfQpsbV9vbmx5IDwtIGNvbXBhcmVfbWV0aG9kcyAlPiUgZmlsdGVyKHNpZ19zdGF0dXM9PSdsbSBzaWcubGltbWEgbm90IHNpZycsIGZpdD09IldpdGhvdXRfdGFnIikgJT4lCiAgZHBseXI6OnNlbGVjdChSb3cubmFtZXMsIGxtX2ZvbGRfY2hhbmdlLCBQLlZhbHVlLCBhZGouUC5WYWwsIGxtX3BfdmFsdWUsIGxtX0JILCBsbV9zdGRfZXJyb3IpICU+JSAjIHNlbGVjdCBjb2x1bW5zCiAgYXJyYW5nZShkZXNjKFAuVmFsdWUpKSAjIGFycmFuZ2UgYnkgbGltbWEgcC12YWx1ZSAoZGVzY2VuZGluZyBvcmRlcikKCnByaW50KGxtX29ubHkpCmBgYAoKTm93IGxldCdzIHBsb3QgdGhlc2UgcHJvdGVpbnMuIE5vdGljZSB0aGF0IGluIGFsbCBjYXNlcywgdGhlIHJlcGxpY2F0ZXMgYXJlIHZlcnkgdGlnaHRseSBkaXN0cmlidXRlZC4KYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0KcHJpbnQocGxvdEludGVuc2l0aWVzKGNvbWJpbmVkX2ludGVuc2l0aWVzW2xtX29ubHkkUm93Lm5hbWVzLF0pKQpgYGAKSW4gc29tZSBjYXNlcywgdGhlIGludGVuc2l0eSB2YWx1ZXMgYXJlIG5lYXIgaWRlbnRpY2FsIChzZWUgYmVsb3cpLiBXaGlsZSBpdCdzIHBvc3NpYmxlIHRoZSBiaW9sb2dpY2FsIHZhcmlhYmlsaXR5IGZvciB0aGlzIHByb3RlaW4gaXMgdmVyeSBsb3csIGl0IHNlZW1zIHVubGlrZWx5IHRoYXQgdGhlIGV4YWN0IHNhbWUgYW1vdW50IG9mIFJOQS1ib3VuZCBwcm90ZWluIHdhcyByZWNvdmVyZWQgZ2l2ZW4gdGhlIGV4cGVjdGVkIHRlY2huaWNhbCB2YXJpYWJpbGl0eSBmcm9tIHRoZSBPT1BTIHByb3RvY29sIGFuZCBzYW1wbGUgcHJlcGFyYXRpb24uIEEgbXVjaCBtb3JlIGxpa2VseSBleHBsYW5hdGlvbiBpcyB0aGF0IHRoZXNlIGludGVuc2l0eSB2YWx1ZXMgYXJlIHNvIHNpbWlsYXIgc2ltcGx5IGJ5IGNoYW5jZS4gVGhpcyBpcyB0aGUgKHJlYXNvbmFibGUpIGFzc3VtcHRpb24gYnkgd2hpY2ggYGxpbW1hYCBhbHRlcnMgdGhlIHN0YW5kYXJkIGRldmlhdGlvbnMgZm9yIHRoZSBjb2VmZmljaWVudHMgdXNpbmcgZmVhdHVyZXMgd2l0aCBzaW1pbGFyIGFidW5kYW5jZS4gCgpgYGB7cn0KIyBIZXRlcm9nZW5lb3VzIG51Y2xlYXIgcmlib251Y2xlb3Byb3RlaW4gVS1saWtlIHByb3RlaW4gMQojIEhOUk5QVUwxCiMgQWN0cyBhcyBhIGJhc2ljIHRyYW5zY3JpcHRpb25hbCByZWd1bGF0b3IuIFJlcHJlc3NlcyBiYXNpYyB0cmFuc2NyaXB0aW9uIGRyaXZlbiBieSBzZXZlcmFsIHZpcnVzIGFuZCBjZWxsdWxhciBwcm9tb3RlcnMuCiMgV2hlbiBhc3NvY2lhdGVkIHdpdGggQlJENywgYWN0aXZhdGVzIHRyYW5zY3JpcHRpb24gb2YgZ2x1Y29jb3J0aWNvaWQtcmVzcG9uc2l2ZSBwcm9tb3RlciBpbiB0aGUgYWJzZW5jZSBvZgojIGxpZ2FuZC1zdGltdWxhdGlvbi4gUGxheXMgYWxzbyBhIHJvbGUgaW4gbVJOQSBwcm9jZXNzaW5nIGFuZCB0cmFuc3BvcnQuIEJpbmRzIGF2aWRseSB0byBwb2x5KEcpIGFuZCBwb2x5KEMpIFJOQQojIGhvbW9wb2x5bWVycyBpbiB2aXRyby4KCnRpZHkoY29tYmluZWRfaW50ZW5zaXRpZXMsIGFkZFBoZW5vPVRSVUUpICU+JQogIGZpbHRlcihnZW5lPT0iUTlCVUoyIiwgVHlwZT09Ik9PUFMiLCBDb25kaXRpb249PSJNIikgJT4lCiAgZHBseXI6OnNlbGVjdChDb25kaXRpb24sIFJlcGxpY2F0ZSwgdmFsdWUpCmBgYAoKQmVsb3csIHdlIGV4cGxvcmUgdGhlIG9ic2VydmVkIGludGVuc2l0eSB2YWx1ZXMgZm9yIHNvbWUgb2YgdGhlIHByb3RlaW5zIHdoaWNoIGFyZSBzaWduaWZpY2FudCBhY2NvcmRpbmcgb25seSB0byBgbGltbWFgLiBOb3RlIHRoYXQgdGhlc2UgaGF2ZSByZWxhdGl2ZWx5IGxhcmdlCmBgYHtyLCBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTB9CmxpbW1hX29ubHkgPC0gY29tcGFyZV9tZXRob2RzICU+JSBmaWx0ZXIoc2lnX3N0YXR1cz09J2xtIG5vdCBzaWcubGltbWEgc2lnJywgZml0PT0iV2l0aG91dF90YWciKSAlPiUKICBkcGx5cjo6c2VsZWN0KFJvdy5uYW1lcywgbG1fZm9sZF9jaGFuZ2UsIFAuVmFsdWUsIGFkai5QLlZhbCwgbG1fcF92YWx1ZSwgbG1fQkgsIGxtX3N0ZF9lcnJvcikgJT4lCiAgYXJyYW5nZShkZXNjKGxtX3BfdmFsdWUpKQoKcHJpbnQoaGVhZChsaW1tYV9vbmx5KSkKCnByaW50KHBsb3RJbnRlbnNpdGllcyhjb21iaW5lZF9pbnRlbnNpdGllc1tsaW1tYV9vbmx5JFJvdy5uYW1lc1sxOjldLF0pKQpgYGAKCkxldCdzIGdvIGJhY2sgdG8gdGhhdCBwbG90IG9mIG1lYW4gdnMgc3FydCBhbmQgc2VlIGhvdyB0aGUgcHJvdGVpbnMgY29tcGFyZSBkZXBlbmRpbmcgb24gd2hldGhlciB0aGV5IHdlcmUgZGV0ZWN0ZWQgYXMgc2lnbmZpY2FudCBpbiBlYWNoIG1ldGhvZC4KCkFzIGV4cGVjdGVkLCB0aG9zZSBzaWduaWZpY2FudCBpbiBqdXN0IGBsbWAgaGF2ZSByZWxhdGl2ZWx5IGxvdyBvYnNlcnZlZCBzdGQuIGRldi4gYW5kIHRob3NlIHNpZ25pZmljYW50IGluIGp1c3QgYGxpbW1hYCBoYXZlIHJlbGF0aXZlbHkgaGlnaCBzdGQuIGRldi4KYGBge3J9Cm1lYW5fc2RfZGF0YSA8LSB0aWR5KHRvdGFsX3Byb3RlaW5fcXVhbnQsIGFkZFBoZW5vPVRSVUUpICU+JSMgInRpZHkiIHRoZSBvYmplY3QsIGUuZyBtYWtlIGl0IGludG8gYSB0aWR5IGRhdGEgZm9ybWF0IC0tPiBsb25nCiAgZ3JvdXBfYnkocHJvdGVpbiwgQ29uZGl0aW9uKSAlPiUgIyBncm91cCBieSBwcm90ZWluIGFuZCBjb25kaXRpb24KICBkcGx5cjo6c3VtbWFyaXNlKG1lYW49bWVhbih2YWx1ZSksIHNxcnRfc2Q9c3FydChzZCh2YWx1ZSkpKSAlPiUgIyBtZWFuIGFuZCBzdGRldiBmb3IgZWFjaCBncm91cAogIHVuZ3JvdXAoKSAlPiUKICBtZXJnZShjb21wYXJlX21ldGhvZHMsIGJ5Lng9InByb3RlaW4iLCBieS55PSJSb3cubmFtZXMiKSAlPiUgIyBtZXJnZSBpbiB0aGUgcmVzdWx0cyBmcm9tIHRoZSB0d28gbWV0aG9kcwogIGZpbHRlcihmaXQ9PSJXaXRob3V0X3RhZyIpICMgT25seSBrZWVwIHRob3NlIHByb3RlaW5zIGZpdHRlZCB3aXRob3V0IHRoZSB0YWcgY292YXJpYXRlIGluIGxtCgojIHJlbWFrZSB0aGUgYmFzaWMgcGxvdCBzaG93aW5nIHRoZSByZWxhdGlvbnNoaXAKcF9iYXNpYyA8LSBtZWFuX3NkX2RhdGEgJT4lCiAgZ2dwbG90KGFlcyhtZWFuLCBzcXJ0X3NkKSkgKyAKICB4bGFiKCJNZWFuIGludGVuc2l0eSIpICsKICB5bGFiKCJDb2VmZmljaWVudCBvZiB2YXJpYW5jZVxuKHNkL21lYW4pIikgKwogIHhsaW0oMCwgTkEpICsgeWxpbSgwLCBOQSkgIyBpbmNsdWRlIDAsMAoKcHJpbnQocF9iYXNpYyArIGdlb21fcG9pbnQoc2l6ZT0wLjIsIGFscGhhPTAuNSkgKyBnZW9tX3Ntb290aChzZT1GQUxTRSkpCgpwX2RlbnNpdHkgPC0gcF9iYXNpYyArCiAgZ2VvbV9kZW5zaXR5XzJkKGFlcyhjb2xvdXI9c2lnX3N0YXR1cyksIGFscGhhPTAuNSwgc2l6ZT0wLjUpICsgIyBkZW5zaXR5IG9mIHBvaW50cwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWNiUGFsZXR0ZVtjKDI6NCw2KV0pIyBzZXQgY29sb3VycwoKcHJpbnQocF9kZW5zaXR5KQoKYGBgCgpGaW5hbGx5LCB3ZSBjYW4gZ2V0IGBsaW1tYWAgdG8gcmV0dXJuIHRoZSBzaWduZmljYW50IGNoYW5nZXMgdXNpbmcgYm90aCBwLXZhbHVlIGFuZCBsb2ctZm9sZCBjaGFuZ2UgdGhyZXNob2xkcyB1c2luZyB0aGUgVFJFQVQgbWV0aG9kIChodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3B1Ym1lZC8xOTE3NjU1MykuIE5vdGUgdGhhdCB0aGlzIGlzIG5vdCB0aGUgc2FtZSBhcyB0aHJlc2hvbGRpbmcgb24gdGhlIHAtdmFsdWUgYW5kIHRoZSBwb2ludCBlc3RpbWF0ZSBmb3IgdGhlIGZvbGQgY2hhbmdlIGFzIGxpbW1hIGlzIGFjdHVhbGx5IHRlc3RpbmcgdGhlIG51bGwgaHlwb3RoZXNpcyB0aGF0IHRoZSBmb2xkIGNoYW5nZSBpcyBsZXNzIHRoYW4gb3VyIHNwZWNpZmllZCB0aHJlc2hvbGQuIFRvIGJlIGV4cGxpY2l0LCBsZXQncyBjaGVjayB0aGUgZGlmZmVyZW5jZQpgYGB7cn0Kc2lnX2NoYW5nZXNfcCA8LSB0b3BUYWJsZShybmFfYmluZGluZ19maXQsIGNvZWYgPSAiY29uZGl0aW9uRzE6dHlwZU9PUFMiLCBuID0gSW5mLCBwLnZhbHVlPTAuMDEsIGFkanVzdC5tZXRob2Q9ImZkciIsIGNvbmZpbnQ9MC45NSkKc2lnX2NoYW5nZXNfcF9sb2dmY19wb2ludF9lc3RpbWF0ZSA8LSBzaWdfY2hhbmdlc19wW2FicyhzaWdfY2hhbmdlc19wJGxvZ0ZDKT4xLF0KY2F0KHNwcmludGYoIiVzIHByb3RlaW5zIHBhc3MgYWRqdXN0ZWQgcC12YWx1ZSB0aHJlc2hvbGQsIG9mIHdoaWNoICVzIHBhc3MgdGhlIHRocmVzaG9sZCBvbiBmb2xkIGNoYW5nZSBwb2ludCBlc3RpbWF0ZVxuIiwgCiAgICAgICAgICAgIG5yb3coc2lnX2NoYW5nZXNfcCksIG5yb3coc2lnX2NoYW5nZXNfcF9sb2dmY19wb2ludF9lc3RpbWF0ZSkpKQoKCnJuYV9iaW5kaW5nX2ZpdF90cmVhdCA8LSB0cmVhdChybmFfYmluZGluZ19maXQsbGZjPTEsdHJlbmQ9VFJVRSkgIyBUZXN0IG51bGwgaHlwb3RoZXNpcyB0aGFuIGNoYW5nZSBpcyA8Mi1mb2xkIApzaWdfY2hhbmdlc19wX2xvZ2ZjIDwtIHRvcFRyZWF0KHJuYV9iaW5kaW5nX2ZpdF90cmVhdCwgY29lZiA9ICJjb25kaXRpb25HMTp0eXBlT09QUyIsIG4gPSBJbmYsCiAgICAgICAgICAgICAgICBwLnZhbHVlPTAuMDEsIGxmYz0xLCBhZGp1c3QubWV0aG9kPSJmZHIiLCBjb25maW50PTAuOTUpCmNhdChzcHJpbnRmKCIlcyBwcm90ZWlucyBwYXNzIHRoZSBjb21iaW5lZCBhZGp1c3RlZCBwLXZhbHVlIHRocmVzaG9sZCArIGZvbGQgY2hhbmdlID4gMlxuIiwgIG5yb3coc2lnX2NoYW5nZXNfcF9sb2dmYykpKQpgYGAKCkFuZCBiZWxvdyB3ZSByZXByb2R1Y2Ugb3VyIHZvbGNhbm8gcGxvdCBpbmNsdWRpbmcgdGhlIDk1JSBjb25maWRlbmNlIGludGVydmFsIGFuZCBoaWdobGlnaHQgdGhvc2UgcHJvdGVpbnMgd2hpY2ggaGF2ZSA8IDElIEZEUiBhbmQgYW4gYWJzb2x1dGUgZm9sZCBjaGFuZ2Ugc2lnbmlmaWNhbnRseSBncmVhdGVyIHRoYW4gMi4gQmVsb3csIHdlIGNhbiBzZWUgdGhhdCBtYW55IG9mIHRoZSBwcm90ZWlucyB3aXRoIGEgZm9sZCBjaGFuZ2UgKEZDKSBwb2ludCBlc3RpbWF0ZSA+IDIgaGF2ZSBhIDk1JSBjb25maWRlbmNlIGludGVydmFsIHRoYXQgb3ZlcmxhcHMgdGhlIGRhc2hlZCBsaW5lcyBmb3IgPjItZm9sZCBjaGFuZ2UuIFRSRUFUIGFsc28gdGFrZXMgdGhlIG11bHRpcGxlIHRlc3RpbmcgaW50byBhY2NvdW50IHNvIGl0J3MgZXZlbiBtb3JlIGNvbnNlcnZhdGl2ZSB0aGFuIGp1c3QgdXNpbmcgdGhlIDk1JSBDSSBzaG93biBiZWxvdy4KYGBge3IsIGZpZy5oZWlnaHQ9NiwgZmlkLndpZHRoPTZ9Ci50bXBfZGYgPC0gYWxsX3JuYV9iaW5kaW5nX3Jlc3VsdHMKLnRtcF9kZiRzaWcgPC0gaWZlbHNlKC50bXBfZGYkUC5WYWx1ZTw9MC4wMSwgIjwxJSBGRFIiLCAiPjElIEZEUiIpICMgYWRkICJzaWciIGNvbHVtbgoudG1wX2RmJHNpZ1tyb3duYW1lcygudG1wX2RmKSAlaW4lIHJvd25hbWVzKHNpZ19jaGFuZ2VzX3BfbG9nZmNfcG9pbnRfZXN0aW1hdGUpXSA8LSAiPDElIEZEUi4gRkMgcG9pbnQgZXN0aW1hdGUgPCAyIgoudG1wX2RmJHNpZ1tyb3duYW1lcygudG1wX2RmKSAlaW4lIHJvd25hbWVzKHNpZ19jaGFuZ2VzX3BfbG9nZmMpXSA8LSAiPDElIEZEUi4gVFJFQVQgRkMgPCAyIgoudG1wX2RmJFNFIDwtIHNxcnQocm5hX2JpbmRpbmdfZml0JHMyLnBvc3QpICogcm5hX2JpbmRpbmdfZml0JHN0ZGV2LnVuc2NhbGVkWywxXQoKcCA8LSAudG1wX2RmICU+JQogIGdncGxvdChhZXMobG9nRkMsIC1sb2cxMChQLlZhbHVlKSwgY29sb3VyPXNpZykpICsKICBnZW9tX3BvaW50KHNpemU9MSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoY2JQYWxldHRlW2MoNiwyLDcpXSwgImdyZXkyMCIpLCBuYW1lPSIiKSArICMgbWFudWFsbHkgYWRqdXN0IGNvbG91cnMKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MSwgbGluZXR5cGU9MiwgY29sb3VyPSJncmV5NzAiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PS0xLCBsaW5ldHlwZT0yLCBjb2xvdXI9ImdyZXk3MCIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249InRvcCIsIGxlZ2VuZC5kaXJlY3Rpb249MikKICAKcHJpbnQocCkKCnByaW50KHAgKyBnZW9tX2Vycm9yYmFyaChhZXMoeG1pbj1DSS5MLCB4bWF4PUNJLlIpKSkKYGBgCk9mIGNvdXJzZSwgdGhlIHRocmVzaG9sZCBmb3IgdGhlIGZvbGQgY2hhbmdlcyB5b3Ugd291bGQgYXJlIGludGVyZXN0ZWQgaW4gCgoKU2F2ZSBvdXQgcmVzdWx0cyBvYmplY3RzIGZvciBsYXRlciBub3RlYm9va3MKYGBge3J9CnNhdmVSRFMoYWxsX3JuYV9iaW5kaW5nX3Jlc3VsdHMsICIuLi9yZXN1bHRzL2xpbW1hX3JuYV9iaW5kaW5nX3Jlc3VsdHMucmRzIikKCnNhdmVSRFMocm5hX2JpbmRpbmdfZml0X3RyZWF0LCAiLi4vcmVzdWx0cy9saW1tYV9ybmFfYmluZGluZ19yZXN1bHRzX3RyZWF0LnJkcyIpCgpzYXZlUkRTKGNvbXBhcmVfbWV0aG9kcywgIi4uL3Jlc3VsdHMvY29tcGFyZV9tZXRob2RzX3JuYV9iaW5kaW5nX3Jlc3VsdHMucmRzIikKYGBgCgoKCg==